"""Alarm service for monitoring device metrics and triggering alerts."""

from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any

from fastapi import HTTPException, status
from sqlalchemy import and_, delete, desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.alarm import AlarmHistory, AlarmRule
from app.models.user import User
from app.schemas.alarm import (
    AlarmHistoryOut,
    AlarmRuleCreate,
    AlarmRuleUpdate,
    BatchAcknowledgeRequest,
    BatchDeleteRequest,
    KeyMetricsOut,
    ResponseTimeMetrics,
)


logger = logging.getLogger(__name__)


@dataclass
class AlarmStats:
    total_alarms: int
    unacknowledged: int
    by_severity: dict[str, int]
    recent_alarms: list[AlarmHistory]


class AlarmService:
    """Alarm rule evaluation and management service."""

    def __init__(self, session: AsyncSession) -> None:
        self.session = session

    # ========== Alarm Rule Management ==========

    async def create_rule(self, data: AlarmRuleCreate, user_id: int | None = None) -> AlarmRule:
        """Create new alarm rule."""
        threshold = data.threshold if data.threshold is not None else data.threshold_high
        if threshold is None:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Threshold value is required")

        operator = data.operator if data.operator is not None else ("gte" if data.alarm_type == "threshold" else "gt")

        duration = data.duration if data.duration is not None else data.duration_threshold or 0
        device_name = data.device_name or f"Device {data.device_id}"

        rule = AlarmRule(
            device_id=data.device_id,
            device_name=device_name,
            metric_name=data.metric_name,
            operator=operator,
            threshold=threshold,
            severity=data.severity,
            duration=duration,
            description=data.description,
            is_active=data.enabled,
            created_by_id=user_id,
        )

        self.session.add(rule)
        await self.session.flush()
        await self.session.refresh(rule)
        return rule

    async def get_rule(self, rule_id: int) -> AlarmRule:
        """Get alarm rule by ID."""
        result = await self.session.execute(select(AlarmRule).where(AlarmRule.id == rule_id))
        rule = result.scalar_one_or_none()
        if not rule:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Alarm rule {rule_id} not found")
        return rule

    async def get_rules(
        self,
        device_id: int | None = None,
        include_disabled: bool = False,
    ) -> list[AlarmRule]:
        """Get alarm rules, optionally filtered by device and enabled state."""
        stmt = select(AlarmRule)

        if device_id is not None:
            stmt = stmt.where(AlarmRule.device_id == device_id)

        if not include_disabled:
            stmt = stmt.where(AlarmRule.is_active.is_(True))

        stmt = stmt.order_by(desc(AlarmRule.created_at))
        result = await self.session.execute(stmt)
        return list(result.scalars().all())

    async def update_rule(self, rule_id: int, data: AlarmRuleUpdate) -> AlarmRule:
        """Update alarm rule."""
        rule = await self.get_rule(rule_id)

        # Update fields if provided
        if data.device_name is not None:
            rule.device_name = data.device_name
        if data.metric_name is not None:
            rule.metric_name = data.metric_name
        if data.operator is not None:
            rule.operator = data.operator
        if data.threshold is not None:
            rule.threshold = data.threshold
        if data.severity is not None:
            rule.severity = data.severity
        if data.duration is not None or data.duration_threshold is not None:
            rule.duration = data.duration if data.duration is not None else data.duration_threshold or 0
        if data.description is not None:
            rule.description = data.description
        if data.enabled is not None:
            rule.is_active = data.enabled

        await self.session.flush()
        await self.session.refresh(rule)
        return rule

    async def delete_rule(self, rule_id: int) -> None:
        """Delete alarm rule."""
        await self.get_rule(rule_id)  # Ensure exists
        await self.session.execute(delete(AlarmRule).where(AlarmRule.id == rule_id))
        await self.session.flush()

    async def batch_delete_rules(self, data: BatchDeleteRequest) -> dict[str, int]:
        """Batch delete alarm rules."""
        deleted_count = 0
        for rule_id in data.rule_ids:
            try:
                await self.delete_rule(rule_id)
                deleted_count += 1
            except HTTPException:
                logger.warning(f"Rule {rule_id} not found, skipping")
                continue

        await self.session.flush()
        return {"deleted": deleted_count, "total": len(data.rule_ids)}

    # ========== Alarm History Management ==========

    async def get_alarm_history(
        self,
        device_id: int | None = None,
        severity: str | None = None,
        acknowledged: bool | None = None,
        start_date: datetime | None = None,
        end_date: datetime | None = None,
    ) -> list[AlarmHistory]:
        """Get alarm history with optional filters."""
        stmt = select(AlarmHistory)

        if device_id is not None:
            stmt = stmt.where(AlarmHistory.device_id == device_id)

        if severity is not None:
            stmt = stmt.where(AlarmHistory.severity == severity)

        if acknowledged is not None:
            stmt = stmt.where(AlarmHistory.acknowledged == acknowledged)

        if start_date is not None:
            stmt = stmt.where(AlarmHistory.triggered_at >= start_date)

        if end_date is not None:
            stmt = stmt.where(AlarmHistory.triggered_at <= end_date)

        stmt = stmt.order_by(desc(AlarmHistory.triggered_at))
        result = await self.session.execute(stmt)
        return list(result.scalars().all())

    async def acknowledge_alarm(self, alarm_id: int, user_id: int, note: str | None = None) -> AlarmHistory:
        """Acknowledge a single alarm."""
        result = await self.session.execute(select(AlarmHistory).where(AlarmHistory.id == alarm_id))
        alarm = result.scalar_one_or_none()

        if not alarm:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Alarm {alarm_id} not found")

        if alarm.acknowledged:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Alarm already acknowledged")

        # Get user to cache username
        user_result = await self.session.execute(select(User).where(User.id == user_id))
        user = user_result.scalar_one_or_none()

        alarm.acknowledged = True
        alarm.acknowledged_at = datetime.utcnow()
        alarm.acknowledged_by_id = user_id
        alarm.acknowledged_by = user_id
        alarm.acknowledged_note = note if note else (user.username if user else None)

        await self.session.flush()
        await self.session.refresh(alarm)
        logger.info("Alarm acknowledged: id=%s user=%s", alarm_id, user_id)
        return alarm

    async def batch_acknowledge_alarms(
        self,
        data: BatchAcknowledgeRequest,
        user_id: int,
        note: str | None = None,
    ) -> dict[str, int]:
        """Batch acknowledge alarms."""
        acknowledged_count = 0
        for alarm_id in data.alarm_ids:
            try:
                await self.acknowledge_alarm(alarm_id, user_id, note=note)
                acknowledged_count += 1
            except HTTPException:
                logger.warning(f"Alarm {alarm_id} not found or already acknowledged, skipping")
                continue

        await self.session.flush()
        return {"acknowledged": acknowledged_count, "total": len(data.alarm_ids)}

    # ========== Statistics ==========

    async def get_alarm_stats(self, device_id: int | None = None, hours: int = 24) -> AlarmStats:
        """Aggregate alarm statistics for a device within a time window."""
        cutoff = datetime.utcnow() - timedelta(hours=hours)

        stmt = select(AlarmHistory).where(AlarmHistory.triggered_at >= cutoff)
        if device_id is not None:
            stmt = stmt.where(AlarmHistory.device_id == device_id)
        stmt = stmt.order_by(desc(AlarmHistory.triggered_at))
        result = await self.session.execute(stmt)
        history = list(result.scalars().all())

        total = len(history)
        unacknowledged = sum(1 for item in history if not item.acknowledged)
        by_severity: dict[str, int] = {}
        for item in history:
            if item.severity is None:
                continue
            by_severity[item.severity] = by_severity.get(item.severity, 0) + 1

        return AlarmStats(
            total_alarms=total,
            unacknowledged=unacknowledged,
            by_severity=by_severity,
            recent_alarms=history,
        )

    async def get_key_metrics(self) -> KeyMetricsOut:
        """Get key alarm metrics."""
        now = datetime.utcnow()
        today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
        yesterday_start = today_start - timedelta(days=1)
        yesterday_end = today_start
        hours_24_ago = now - timedelta(hours=24)

        # Today's alarm count
        today_stmt = select(func.count()).select_from(AlarmHistory).where(AlarmHistory.triggered_at >= today_start)
        today_result = await self.session.execute(today_stmt)
        today_count = today_result.scalar_one()

        # Yesterday's alarm count
        yesterday_stmt = (
            select(func.count())
            .select_from(AlarmHistory)
            .where(
                and_(
                    AlarmHistory.triggered_at >= yesterday_start,
                    AlarmHistory.triggered_at < yesterday_end,
                )
            )
        )
        yesterday_result = await self.session.execute(yesterday_stmt)
        yesterday_count = yesterday_result.scalar_one()

        # Unacknowledged count
        unack_stmt = select(func.count()).select_from(AlarmHistory).where(AlarmHistory.acknowledged == False)  # noqa: E712
        unack_result = await self.session.execute(unack_stmt)
        unacknowledged_count = unack_result.scalar_one()

        # Critical alarms in last 24 hours
        critical_stmt = (
            select(func.count())
            .select_from(AlarmHistory)
            .where(
                and_(
                    AlarmHistory.triggered_at >= hours_24_ago,
                    AlarmHistory.severity == "critical",
                )
            )
        )
        critical_result = await self.session.execute(critical_stmt)
        critical_count_24h = critical_result.scalar_one()

        # Active rules count
        active_rules_stmt = select(func.count()).select_from(AlarmRule).where(AlarmRule.is_active == True)  # noqa: E712
        active_rules_result = await self.session.execute(active_rules_stmt)
        active_rules_count = active_rules_result.scalar_one()

        # Response time metrics
        response_metrics = await self._get_response_time_metrics()

        return KeyMetricsOut(
            today_count=today_count,
            today_change=today_count - yesterday_count,
            unacknowledged_count=unacknowledged_count,
            critical_count_24h=critical_count_24h,
            active_rules_count=active_rules_count,
            response_time_metrics=response_metrics,
        )

    async def _get_response_time_metrics(self) -> ResponseTimeMetrics:
        """Calculate response time metrics for acknowledged alarms."""
        # Get acknowledged alarms with triggered_at and acknowledged_at
        stmt = (
            select(AlarmHistory)
            .where(
                and_(
                    AlarmHistory.acknowledged == True,  # noqa: E712
                    AlarmHistory.acknowledged_at.isnot(None),
                )
            )
            .order_by(desc(AlarmHistory.acknowledged_at))
            .limit(1000)  # Last 1000 acknowledged alarms
        )
        result = await self.session.execute(stmt)
        alarms = list(result.scalars().all())

        if not alarms:
            return ResponseTimeMetrics(
                avg_ack_time=None,
                min_ack_time=None,
                max_ack_time=None,
                ack_rate=None,
            )

        # Calculate acknowledgment times in minutes
        ack_times = []
        for alarm in alarms:
            if alarm.triggered_at and alarm.acknowledged_at:
                delta = alarm.acknowledged_at - alarm.triggered_at
                minutes = delta.total_seconds() / 60
                ack_times.append(minutes)

        if not ack_times:
            return ResponseTimeMetrics(
                avg_ack_time=None,
                min_ack_time=None,
                max_ack_time=None,
                ack_rate=None,
            )

        # Calculate acknowledgment rate (last 24 hours)
        hours_24_ago = datetime.utcnow() - timedelta(hours=24)
        total_stmt = select(func.count()).select_from(AlarmHistory).where(AlarmHistory.triggered_at >= hours_24_ago)
        total_result = await self.session.execute(total_stmt)
        total_24h = total_result.scalar_one()

        acked_stmt = (
            select(func.count())
            .select_from(AlarmHistory)
            .where(
                and_(
                    AlarmHistory.triggered_at >= hours_24_ago,
                    AlarmHistory.acknowledged == True,  # noqa: E712
                )
            )
        )
        acked_result = await self.session.execute(acked_stmt)
        acked_24h = acked_result.scalar_one()

        ack_rate = (acked_24h / total_24h * 100) if total_24h > 0 else 0

        return ResponseTimeMetrics(
            avg_ack_time=sum(ack_times) / len(ack_times),
            min_ack_time=min(ack_times),
            max_ack_time=max(ack_times),
            ack_rate=ack_rate,
        )

    async def get_severity_distribution(self, days: int = 7) -> dict[str, Any]:
        """Get alarm count by severity for last N days."""
        start_time = datetime.utcnow() - timedelta(days=days)

        # Get counts by severity
        stmt = (
            select(AlarmHistory.severity, func.count(AlarmHistory.id))
            .where(AlarmHistory.triggered_at >= start_time)
            .group_by(AlarmHistory.severity)
        )
        result = await self.session.execute(stmt)
        rows = result.all()

        # Format for PieChart
        data = [{"name": row[0].capitalize(), "value": row[1]} for row in rows]

        return {"data": data}

    async def get_24h_trend(self) -> dict[str, Any]:
        """Get hourly alarm count for last 24 hours."""
        now = datetime.utcnow()
        hours_24_ago = now - timedelta(hours=24)

        # Get alarms in last 24 hours
        stmt = select(AlarmHistory).where(AlarmHistory.triggered_at >= hours_24_ago).order_by(AlarmHistory.triggered_at)
        result = await self.session.execute(stmt)
        alarms = list(result.scalars().all())

        # Group by hour
        hour_counts: dict[str, int] = {}
        for i in range(24):
            hour_time = hours_24_ago + timedelta(hours=i)
            hour_str = hour_time.strftime("%H:00")
            hour_counts[hour_str] = 0

        for alarm in alarms:
            hour_str = alarm.triggered_at.strftime("%H:00")
            if hour_str in hour_counts:
                hour_counts[hour_str] += 1

        # Format for LineChart
        categories = sorted(hour_counts.keys())
        series = [{"name": "Alarms", "data": [hour_counts[hour] for hour in categories]}]

        return {"categories": categories, "series": series}

    async def get_top_devices(self, limit: int = 10, days: int = 7) -> dict[str, Any]:
        """Get top N devices by alarm count."""
        start_time = datetime.utcnow() - timedelta(days=days)

        # Get counts by device
        stmt = (
            select(AlarmHistory.device_name, func.count(AlarmHistory.id))
            .where(AlarmHistory.triggered_at >= start_time)
            .group_by(AlarmHistory.device_name)
            .order_by(desc(func.count(AlarmHistory.id)))
            .limit(limit)
        )
        result = await self.session.execute(stmt)
        rows = result.all()

        # Format for PieChart
        data = [{"name": row[0], "value": row[1]} for row in rows]

        return {"data": data}

    async def get_hourly_distribution(self, days: int = 7) -> dict[str, Any]:
        """Get alarm count by hour of day for last N days."""
        start_time = datetime.utcnow() - timedelta(days=days)

        # Get alarms
        stmt = select(AlarmHistory).where(AlarmHistory.triggered_at >= start_time)
        result = await self.session.execute(stmt)
        alarms = list(result.scalars().all())

        # Group by hour of day (0-23)
        hour_counts = {f"{h:02d}:00": 0 for h in range(24)}

        for alarm in alarms:
            hour_str = f"{alarm.triggered_at.hour:02d}:00"
            hour_counts[hour_str] += 1

        # Format for LineChart
        categories = [f"{h:02d}:00" for h in range(24)]
        series = [{"name": "Alarms", "data": [hour_counts[hour] for hour in categories]}]

        return {"categories": categories, "series": series}
